/*
 * Created on 04/12/2007
 *
 */
package org.seginfo.httpfuzzer;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Iterator;
import java.util.Observable;
import java.util.Observer;

import org.apache.log4j.Logger;
import org.seginfo.httpfuzzer.generation.CasesGenerator;

/**
 * @author Matu
 */
public class Fuzzer implements Observer {
	
	private Logger logger;
	
	private String address;
	private int n;
	private int threads;
	private String caseId;
	private String directory;
	private boolean fuzzHTTPMethod;
	private boolean fuzzHTTPVersion;
	private boolean stop;
	
	private boolean running;

	/**
	 * @param address
	 * @param n
	 * @param threads
	 * @param caseId
	 * @param directory
	 * @param fuzzHTTPMethod
	 * @param fuzzHTTPVersion
	 * @param stop
	 */
	public Fuzzer(
			String address,
			int n,
			int threads,
			String caseId,
			String directory,
			boolean fuzzHTTPMethod,
			boolean fuzzHTTPVersion,
			boolean stop) {
		
		this.logger = Logger.getLogger(Main.class);
		
		this.address = address;
		this.n = n;
		this.threads = threads;
		this.caseId = caseId;
		this.directory = directory;
		this.fuzzHTTPMethod = fuzzHTTPMethod;
		this.fuzzHTTPVersion = fuzzHTTPVersion;
		this.stop = stop;
		
		this.running = true;
	}
	
	/**
	 * Execute this fuzzer
	 */
	public void execute() {
		
		/* Execute stored case */
		if (this.caseId != null) {
			this.runFuzzCase(this.caseId, this.address, this.directory);
		}
		
		/* Generate fuzzing cases */
		else {
			this.generateFuzzCases(
					this.n,
					this.address,
					this.threads,
					this.directory,
					this.fuzzHTTPMethod,
					this.fuzzHTTPVersion,
					this.stop);
		}
	}

	/**
	 * @param id
	 * @param address
	 * @param directory
	 */
	private void runFuzzCase(String id, String address, String directory) {
		
		URL url = null;
		try {
			url = new URL("http://" + address + "/");
		} catch (MalformedURLException e) {
			logger.error("The provided server address is invalid.",e);
			System.exit(0);
		}
		
		CaseLogger caseLogger = new CaseLogger();
		
		FuzzCase fuzzCase = loadCase(id,new File(directory));
		if (fuzzCase == null) {
			logger.error("The specified case (" + id + ") could not be loaded");
			System.exit(0);
		}
		logger.info("Starting attack to " + url.getHost());
		CaseRunner caseRunner = new CaseRunner(url, fuzzCase, caseLogger);
		caseRunner.addObserver(this);
		Thread runnerThread = new Thread(caseRunner);
		runnerThread.setName("Running case: " + fuzzCase.getId());
		runnerThread.start();
	}
	
	/**
	 * @param n
	 * @param address
	 * @param threads
	 * @param directory
	 */
	private void generateFuzzCases(int n, String address, int threads, String directory, boolean fuzzHTTPMethod, boolean fuzzHTTPVersion, boolean stop) {
		
		URL url = null;
		try {
			// IIS 4.0: www.legischubut.gov.ar
			// www.errepar.com.ar: Microsoft-IIS/5.0
			// www.uar.com.ar: Microsoft-IIS/6.0
			// www.radiotaxi-mendoza.com.ar: Microsoft-IIS/6.0
			// www.pisteros.com.ar: Microsoft-IIS/5.0
			url = new URL("http://" + address + "/");
		} catch (MalformedURLException e) {
			logger.error("The provided server address is invalid.",e);
			System.exit(0);
		}
		
		logger.info("Starting attack to " + url.getHost());
		
		/* URL de la muerte */
		//tyupoiupoiupoiuuuuuuuuuuufffffffffffffffffffffffffffiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiifffffffffffffffffffffffffffffpppppppppppppppppppppppppppppppssssssssssssssssssssssssssskkkkkkkkkkkkkkkkkkkkkkkkkkkkkkfffffffffffffffffffffffffffffffffflllllllllllllllllllllllllllllllllllllllxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx 
		
		CaseLogger caseLogger = new CaseLogger();
		
		Iterator iterator = new CasesGenerator().generateCases(n,fuzzHTTPMethod,fuzzHTTPVersion);
		while (iterator.hasNext() && this.running) {
			while (Thread.activeCount() > threads) {
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
				}
			}
			FuzzCase fuzzCase = (FuzzCase) iterator.next();
			CaseRunner caseRunner = new CaseRunner(url, fuzzCase, caseLogger);
			caseRunner.addObserver(this);
			storeCase(fuzzCase,new File(directory));
			Thread runnerThread = new Thread(caseRunner);
			runnerThread.setName("Running case: " + fuzzCase.getId());
			runnerThread.start();
		}
		
		if (!this.running) {
			System.out.println("Stopped because of connection was refused.");
		}
	}
	
	/**
	 * @param fuzzCase
	 */
	private synchronized boolean storeCase(FuzzCase fuzzCase, File directory) {
		if (directory.exists() && directory.isDirectory()) {
			try {
				ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File(directory.getAbsolutePath() + '\\' + fuzzCase.getId() + ".case")));
				outputStream.writeObject(fuzzCase);
				outputStream.close();
			} catch (FileNotFoundException e) {
				return false;
			} catch (IOException e) {
				return false;
			}
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * @param caseId
	 * @return
	 */
	private synchronized FuzzCase loadCase(String caseId, File directory) {
		FuzzCase fuzzCase = null;
		if (directory.exists() && directory.isDirectory()) {
			try {
				ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File(directory.getAbsolutePath() + '\\' + caseId + ".case")));
				fuzzCase = (FuzzCase) inputStream.readObject();
				inputStream.close();
			} catch (FileNotFoundException e) {
			} catch (IOException e) {
			} catch (ClassNotFoundException e) {
			}
		}
		return fuzzCase;
	}
	
	/**
	 * @see java.util.Observer#update(java.util.Observable, java.lang.Object)
	 */
	public void update(Observable observable, Object parameter) {
		
		/* Stop on refused connection */
		if (this.stop) {
			if (parameter instanceof FuzzCase) {
				FuzzCase fuzzCase = (FuzzCase) parameter;
				if (fuzzCase.getResult() == FuzzCase.CONNECTION_REFUSED) {
					this.running = false;
				}
			}
		}
		
		/* Remove observer */
		observable.deleteObserver(this);
	}
}
